package org.jprobe.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jprobe.reflect.classpath.ClassPathInterface;
import org.jprobe.reflect.classpath.ClassPathReader;
import org.jprobe.reflect.classpath.Instantiator;
/**
 * java code generator
 * @author Hussain Al-Mutawa
 * @version 1.0
 */
public class CodeGenerator implements CodeGeneratorInterface {
	/** the classpath  **/
	private transient static final ClassPathInterface CLASSPATH = ClassPathReader.instance.getClassPath();
	/**
	 * a map which contains the primitive types dummy values
	 */
	private static transient final Map<Class<?>,String> PRIMITIVES = new HashMap<Class<?>,String>();
	static{
		PRIMITIVES.put(int.class	,"1");
		PRIMITIVES.put(char.class	,"'\u0000'");
		PRIMITIVES.put(float.class	,"1.0f");
		PRIMITIVES.put(long.class	,"1L");
		PRIMITIVES.put(double.class	,"1.0d");
		PRIMITIVES.put(byte.class	,"(byte)1");
		PRIMITIVES.put(short.class	,"(short)1");
		PRIMITIVES.put(boolean.class,"true");
		PRIMITIVES.put(String.class	,"\"\"");
		PRIMITIVES.put(Object.class	,"new Object()");
	}


	@Override
	public String getBinaryName(final Class<?>type){
		final String result;
		if(type.isArray()){
			result = getBinaryName(type.getComponentType())+"[]";
		}else if(type.isPrimitive() || type.getPackage()==null){
			result = type.getSimpleName();
		}else if(Void.TYPE.equals(type)){
			result = "void";
		}else{
			result = type.getName().replace('$', '.');
		}
		return result;
	}

	@Override
	public String getArrayAsString(final Class<?> array) throws Exception{
		if(!array.isArray()){
			throw new IllegalArgumentException(array+" is not array");
		}
		String result="ITEM";
		Class<?>clazz=array;
		while(clazz.isArray()){
			clazz=clazz.getComponentType();
			result='{'+result+'}';
		}
		final String item = getImplementation(clazz)[1];
		result=result.replace("ITEM", item);
		return "new "+getBinaryName(array) + result;
	}

	@Override
	public String getInstantiationCode(final Class<?> clazz) throws Exception{

		final String result;
		if(clazz==null){
			throw new IllegalArgumentException("arguments [null,void] are not accepted");
		}else if(clazz==Void.TYPE){
			result="";
		}else{
			final String[]impl=getImplementation(clazz);
			final String pkg=impl[0];
			final String implementation=impl[1];
			final String objectName = getBinaryName(clazz);
			result="package "+pkg+";\n"+
			"public final class " + clazz.getSimpleName().replaceAll("\\[\\]", "A") + "JProbe{\n"+
			"\tpublic " + objectName + " $(){\n\t\treturn "+ implementation +";\n\t}\n"+
			"}";
		}
		return result;
	}
	@Override
	public String[] getImplementation(Class<?> clazz) throws Exception{
		final String implementation;
		final String pkg;
		final String objectName = getBinaryName(clazz);
		if(PRIMITIVES.containsKey(clazz)){
			implementation = PRIMITIVES.get(clazz);
			pkg="impl";
		}else if(clazz.isArray()){
			implementation = getArrayAsString(clazz);
			Class<?>comp=clazz;
			do{comp=comp.getComponentType();}while(comp.isArray());
			if(CLASSPATH.isRuntimeClass(comp)){
				pkg="impl"+(clazz.getPackage()==null?"":'.'+comp.getPackage().getName());
			}else{
				pkg=comp.getPackage().getName();
			}
		}else{
			final String packageName = clazz.getPackage().getName();

			final Constructor<?>constructor=new Instantiator().getFirstPublicConstructor(clazz);
			Method factoryCreator=null;
			if(CLASSPATH.isRuntimeClass(clazz)){
				final boolean noDefinedConstrctors=(constructor==null);
				final boolean notInterface = !clazz.isInterface();
				if(notInterface  && noDefinedConstrctors){
					final List<Method>factoryCreators=new Instantiator().getFactoryCreatorMethod(clazz);
					if(factoryCreators.isEmpty()){
						throw new InstantiationException(clazz+" does not have a public constructor and is a java specific class, and no static member [singleton] is available to create it");
					}else{
						factoryCreator = factoryCreators.get(0);
					}
				}
				pkg = "impl."+packageName;
			}else{
				pkg = packageName;
			}

			if(constructor==null && factoryCreator==null && clazz.isInterface()){
				implementation = "new " + objectName + "(){\n"+
				getDummyMethodsImplementation(clazz,clazz)+
				"\n\t}";
			}else if(constructor!=null){
				final String params = getParametersDeclaration(constructor.getParameterTypes());
				if(Modifier.isAbstract(constructor.getDeclaringClass().getModifiers())){
					implementation = "new " + constructor.getName() + "("+params.toString()+"){\n"+
					getDummyMethodsImplementation(clazz,constructor.getDeclaringClass())+
					"\n\t}";
				}else{
					implementation = "new " + constructor.getName() + "("+params.toString()+")";
				}
			}else if(factoryCreator!=null){
				final String params = getParametersDeclaration(factoryCreator.getParameterTypes());
				implementation = factoryCreator.getDeclaringClass().getName() +'.'+  factoryCreator.getName() + "("+params+")";
			}else{
				/* DEAD END */
				//if the constructor is null, which might be a rare case
				//then find a factory elsewhere which creates this object
				throw new UnsupportedOperationException("this class ["+clazz+"] can never be initialized, sorry");
			}
		}
		return new String[]{pkg,implementation};
	}

	@Override
	public String getParametersDeclaration(final Class<?>[]types)throws Exception{
		//System.err.println(Arrays.toString(types));
		final StringBuffer params = new StringBuffer();
		//get the parameters of the initializer
		for(final Class<?>ctype:types){
			params.append(getImplementation(ctype)[1]).append(',');
		}
		if(params.toString().endsWith(",")){
			params.setLength(params.toString().length()-1);
		}
		return params.toString();
	}

	@Override
	public String getDummyMethodsImplementation(final Class<?> initializeWhat,Class<?>initializedBy) throws Exception{
		final StringBuffer contents = new StringBuffer(100);
		final Method[]methods = new UnimplementedMethodsExplorer(initializedBy).getAbstractMethods();
		for(final Method method:methods){
			final String params = getSignature(method);
			final String var;
			final Class<?>rType = method.getReturnType();
			final String bname = getBinaryName(method.getReturnType());
			if(Void.TYPE==rType){
				var = "";
			}else if(PRIMITIVES.containsKey(rType)){
				var = PRIMITIVES.get(rType);
			}else{
				var = "("+bname+")null";
			}
			contents.append(
					"\t\t\t@Override public " + bname + " " + method.getName() + "("+params+"){\n"+
					"\t\t\t\treturn "+ var +";\n\t\t\t}\n"
			);
		}
		return contents.toString();
	}

	@Override
	public String getSignature(final Method method) {
		final StringBuffer sbuffer = new StringBuffer();

		int counter=0;
		for(final Class<?>clazz:method.getParameterTypes()){
			sbuffer.append(getBinaryName(clazz)+" p"+(++counter)+clazz.getSimpleName().replaceAll("\\[\\]", "Array")+",");
		}
		if(sbuffer.toString().endsWith(",")){
			sbuffer.setLength(sbuffer.toString().length()-1);
		}
		return sbuffer.toString();
	}

}